"""Walk-Forward 回测框架."""

from __future__ import annotations

import logging
from collections import deque
from dataclasses import dataclass
from typing import Deque, Dict, List

import numpy as np
import pandas as pd

from ..models.base import BaseQuantModel
from .config import BacktestConfig
from .executor import TradeExecutor
from .signals import SignalGenerator

logger = logging.getLogger(__name__)


@dataclass
class BacktestResult:
    """回测结果."""

    # 净值曲线
    equity_curve: pd.Series  # 日期 -> 净值

    # 交易记录
    trades: List  # Trade 对象列表

    # 持仓记录
    positions: List  # Position 对象列表

    # 每日收益
    daily_returns: pd.Series  # 日期 -> 日收益率

    # 统计信息
    total_trades: int
    win_rate: float
    stop_loss_count: int
    take_profit_count: int


class WalkForwardBacktester:
    """Walk-Forward 回测器."""

    def __init__(self, config: BacktestConfig) -> None:
        self.config = config
        self.signal_generator = SignalGenerator(config)
        self.executor = TradeExecutor(config)
        # 基准策略实例将在需要时延迟初始化，避免循环导入
        self._baseline_strategy = None

    def backtest(
        self,
        model: BaseQuantModel,
        X: pd.DataFrame,
        y: pd.Series | None,
        prices: pd.DataFrame,
        train_start_idx: int,
        train_end_idx: int,
        test_start_idx: int,
        test_end_idx: int,
    ) -> BacktestResult:
        """执行单次回测窗口.

        Parameters
        ----------
        model:
            已训练的模型
        X:
            特征数据（全量）
        y:
            标签数据（可选，用于验证）
        prices:
            价格数据，包含 open, high, low, close 列
        train_start_idx:
            训练数据起始索引
        train_end_idx:
            训练数据结束索引
        test_start_idx:
            测试数据起始索引
        test_end_idx:
            测试数据结束索引

        Returns
        -------
        回测结果
        """
        # 准备测试数据
        X_test = X.iloc[test_start_idx:test_end_idx]
        prices_test = prices.iloc[test_start_idx:test_end_idx]

        # 在回测前检查模型预测概率分布
        # 如果所有预测概率都低于买入阈值或高于卖出阈值，可能无法生成有效交易信号
        try:
            if len(X_test) > 0:
                # 对于传统机器学习模型，使用前几个样本检查
                if not any(dl_name in model.config.name.lower() for dl_name in ["lstm", "gru", "cnn"]):
                    sample_X = X_test.iloc[:min(10, len(X_test))]
                    sample_proba = model.predict_proba(sample_X)
                    if len(sample_proba) > 0:
                        max_proba = float(np.max(sample_proba))
                        min_proba = float(np.min(sample_proba))
                        mean_proba = float(np.mean(sample_proba))
                        
                        # 检查是否所有预测概率都在阈值范围内（无法生成交易信号）
                        buy_threshold = self.config.buy_threshold
                        sell_threshold = self.config.sell_threshold
                        
                        if max_proba <= buy_threshold and min_proba >= sell_threshold:
                            logger.warning(
                                f"模型 {model.config.name} 在测试数据上的预测概率范围 [{min_proba:.4f}, {max_proba:.4f}] "
                                f"全部在交易阈值范围内（买入阈值={buy_threshold:.4f}, 卖出阈值={sell_threshold:.4f}），"
                                f"可能无法生成有效的交易信号。平均概率={mean_proba:.4f}"
                            )
                        elif max_proba < buy_threshold:
                            logger.warning(
                                f"模型 {model.config.name} 在测试数据上的最大预测概率 {max_proba:.4f} < 买入阈值 {buy_threshold:.4f}，"
                                f"可能无法生成买入信号。平均概率={mean_proba:.4f}"
                            )
                        elif min_proba > sell_threshold:
                            logger.warning(
                                f"模型 {model.config.name} 在测试数据上的最小预测概率 {min_proba:.4f} > 卖出阈值 {sell_threshold:.4f}，"
                                f"可能无法生成卖出信号。平均概率={mean_proba:.4f}"
                            )
        except Exception as e:
            logger.debug(f"检查预测概率分布时出错（不影响回测）: {e}")

        # 检查数据量是否足够（仅对深度学习模型）
        # 深度学习模型（LSTM、GRU、CNN）需要 sequence_len 个交易日的数据
        # 传统机器学习模型只需要1个交易日即可
        is_deep_learning_model = False
        min_required_length = 1  # 传统机器学习模型只需要1个交易日
        
        # 检查是否是深度学习模型
        model_name_lower = model.config.name.lower() if hasattr(model, "config") and hasattr(model.config, "name") else ""
        if any(dl_name in model_name_lower for dl_name in ["lstm", "gru", "cnn"]):
            is_deep_learning_model = True
            min_required_length = 30  # 默认序列长度
            
            # 尝试从模型配置中获取实际的序列长度
            if hasattr(model, "config") and hasattr(model.config, "params"):
                min_required_length = model.config.params.get("sequence_len", 30)
            elif hasattr(model, "config") and isinstance(model.config, dict):
                min_required_length = model.config.get("params", {}).get("sequence_len", 30)
        
        # 只对深度学习模型检查数据量
        if is_deep_learning_model and len(X_test) < min_required_length:
            raise ValueError(
                f"测试窗口数据不足: 深度学习模型需要至少 {min_required_length} 个交易日，"
                f"但只有 {len(X_test)} 个交易日"
            )

        # 初始化回测状态
        capital = self.config.initial_capital
        position = 0.0  # 0=空仓，1=满仓
        entry_price = 0.0
        entry_capital = 0.0  # 买入时的资金，用于计算持仓价值
        equity_values = []
        daily_returns = []
        probabilities = []  # 存储每日预测概率

        # 重置执行器
        self.executor.trades = []
        self.executor.positions = []

        signal_queue: Deque[int] = deque()

        # 逐日回测（修复Look-Ahead Bias：t日预测、t+1日执行）
        for i, (date, row) in enumerate(prices_test.iterrows()):
            # 根据模型类型选择预测方式
            if is_deep_learning_model:
                # 深度学习模型：使用从开始到当前日期的序列数据
                # 需要至少min_required_length个交易日的数据
                if i < min_required_length:
                    # 数据不足，跳过预测（或使用默认值）
                    proba = 0.5  # 中性预测
                else:
                    # 使用从开始到当前日期的数据
                    X_for_prediction = X_test.iloc[:i+1]
                    # 对于序列模型，需要确保有足够的序列长度
                    if len(X_for_prediction) >= min_required_length:
                        # 只使用最后min_required_length个交易日的数据（滑动窗口）
                        X_window = X_for_prediction.iloc[-min_required_length:]
                        proba_array = model.predict_proba(X_window)
                        # 取最后一个预测值（对应当前日期）
                        proba = proba_array[-1] if len(proba_array) > 0 else 0.5
                    else:
                        proba = 0.5
            else:
                # 传统机器学习模型：只使用当天的特征数据
                X_today = X_test.iloc[i:i+1]
                proba_array = model.predict_proba(X_today)
                proba = proba_array[0] if len(proba_array) > 0 else 0.5
            
            probabilities.append(proba)
            last_proba = proba
            
            # 当前预测对应的信号入队
            current_signal = self.signal_generator.generate_signals(np.array([proba]))[0]
            signal_queue.append(current_signal)

            # 当队列长度未达到 signal_lag+1 时，仅记录市值（无交易）
            if len(signal_queue) <= self.config.signal_lag:
                close_price = row["close"]
                if position > 0 and entry_price > 0:
                    current_value = entry_capital * (close_price / entry_price)
                else:
                    current_value = capital
                equity_values.append(current_value)
                prev_value = equity_values[-2] if len(equity_values) > 1 else current_value
                daily_return = (current_value - prev_value) / prev_value if prev_value > 0 else 0.0
                daily_returns.append(daily_return)
                self.executor.update_position(date, close_price, position, entry_price, current_value)
                continue

            # 取出滞后信号执行
            signal = signal_queue.popleft()
            close_price = row["close"]
            high = row["high"]
            low = row["low"]
            open_price = row.get("open", close_price)

            # 下一日执行的信号由队列中的最新值决定（不在此处更新 pending）
            high = row["high"]
            low = row["low"]

            # 检查止损/止盈（如果有持仓）
            if position > 0:
                # 计算当前持仓价值（用于止损/止盈检查）
                current_position_value = entry_capital * (close_price / entry_price)
                # 传入当前价格，用于正确计算持仓数量
                position, capital, _ = self.executor.check_stop_loss_take_profit(
                    date, high, low, position, entry_price, current_position_value, close_price
                )
                if position == 0:
                    # 卖出后，将持仓价值转换为现金
                    # capital 已经在 check_stop_loss_take_profit 中更新
                    entry_price = 0.0
                    entry_capital = 0.0

            # 根据信号执行交易
            if signal == 1 and position == 0:
                # 买入信号
                # 记录买入前的资金
                entry_capital = capital
                position, capital, _ = self.executor.execute_trade(
                    date, "buy", open_price, position, capital
                )
                if position > 0:
                    entry_price = open_price * (1 + self.config.slippage_rate)
                    # 买入后，capital 变为 0（已全部买入），但 entry_capital 保存了买入时的资金
            elif signal == -1 and position > 0:
                # 卖出信号
                # 计算当前持仓价值
                current_position_value = entry_capital * (open_price / entry_price)
                # 使用持仓价值作为 capital 传入执行器，并传入当前价格用于计算数量
                position, capital, _ = self.executor.execute_trade(
                    date, "sell", open_price, position, current_position_value, entry_price, open_price
                )
                if position == 0:
                    # 卖出后，capital 已经更新为卖出后的现金
                    entry_price = 0.0
                    entry_capital = 0.0

            # 更新持仓价值
            if position > 0:
                # 使用买入时的资金和当前价格计算持仓价值
                current_value = entry_capital * (close_price / entry_price)
            else:
                # 空仓时，价值就是现金
                current_value = capital

            equity_values.append(current_value)
            if i > 0:
                prev_value = equity_values[i - 1]
                # 避免除零错误
                if prev_value > 0:
                    daily_return = (current_value - prev_value) / prev_value
                else:
                    daily_return = 0.0
            else:
                daily_return = 0.0
            daily_returns.append(daily_return)

            # 更新持仓记录
            # 计算当前持仓价值或现金用于记录
            if position > 0:
                position_value = entry_capital * (close_price / entry_price)
            else:
                position_value = capital
            self.executor.update_position(date, close_price, position, entry_price, position_value)

        # 构建结果
        equity_curve = pd.Series(equity_values, index=prices_test.index)
        daily_returns_series = pd.Series(daily_returns, index=prices_test.index)

        # 统计交易信息
        trades = self.executor.trades
        total_trades = len(trades)
        win_trades = sum(1 for t in trades if t.action == "sell" and t.total_cost < 0)
        win_rate = win_trades / total_trades if total_trades > 0 else 0.0

        stop_loss_count = sum(
            1
            for t in trades
            if t.action == "sell" and entry_price > 0 and abs(t.price / entry_price - 1) <= self.config.stop_loss_pct + 0.01
        )
        take_profit_count = sum(
            1
            for t in trades
            if t.action == "sell" and entry_price > 0 and abs(t.price / entry_price - 1) >= self.config.take_profit_pct - 0.01
        )

        # 检查是否有交易发生
        if total_trades == 0:
            model_name = model.config.name if hasattr(model, "config") and hasattr(model.config, "name") else "未知模型"
            logger.warning(
                f"模型 {model_name} 在回测窗口 [{test_start_idx}:{test_end_idx}] 上未生成任何交易信号，"
                f"回测指标将全部为0。可能原因："
                f"1) 预测概率全部低于买入阈值 {self.config.buy_threshold:.4f} 或高于卖出阈值 {self.config.sell_threshold:.4f}；"
                f"2) 特征选择后模型性能下降；"
                f"3) 数据质量问题。"
            )
            # 检查预测概率分布
            if len(probabilities) > 0:
                proba_array = np.array(probabilities)
                max_proba = float(np.max(proba_array))
                min_proba = float(np.min(proba_array))
                mean_proba = float(np.mean(proba_array))
                logger.warning(
                    f"预测概率统计: 最大值={max_proba:.4f}, 最小值={min_proba:.4f}, "
                    f"平均值={mean_proba:.4f}, 买入阈值={self.config.buy_threshold:.4f}, "
                    f"卖出阈值={self.config.sell_threshold:.4f}"
                )

        return BacktestResult(
            equity_curve=equity_curve,
            trades=trades,
            positions=self.executor.positions,
            daily_returns=daily_returns_series,
            total_trades=total_trades,
            win_rate=win_rate,
            stop_loss_count=stop_loss_count,
            take_profit_count=take_profit_count,
        )

    def walk_forward_backtest(
        self,
        models: Dict[str, BaseQuantModel],
        X: pd.DataFrame,
        prices: pd.DataFrame,
        train_start_idx: int = 0,
    ) -> Dict[str, List[BacktestResult]]:
        """执行 Walk-Forward 回测.

        Parameters
        ----------
        models:
            模型字典，键为模型名称，值为已训练的模型
        X:
            特征数据（全量）
        prices:
            价格数据
        train_start_idx:
            训练起始索引

        Returns
        -------
        每个模型的回测结果列表
        """
        results: Dict[str, List[BacktestResult]] = {name: [] for name in models.keys()}
        baseline_results: List[BacktestResult] = []

        n = len(X)
        current_train_end = train_start_idx

        while current_train_end + self.config.window_length < n:
            # 定义窗口
            window_start = current_train_end
            window_end = min(current_train_end + self.config.window_length, n)
            test_start = window_end
            test_end = min(test_start + self.config.step_size, n)

            if test_start >= n:
                break

            logger.debug(
                f"Walk-Forward 窗口: 训练 [{window_start}:{window_end}], "
                f"测试 [{test_start}:{test_end}]"
            )

            # 对每个模型进行回测
            for model_name, model in models.items():
                try:
                    # 检查是否是深度学习模型，以及数据是否足够
                    is_deep_learning_model = False
                    min_required_length = 1
                    
                    model_name_lower = model.config.name.lower() if hasattr(model, "config") and hasattr(model.config, "name") else ""
                    if any(dl_name in model_name_lower for dl_name in ["lstm", "gru", "cnn"]):
                        is_deep_learning_model = True
                        min_required_length = 30
                        if hasattr(model, "config") and hasattr(model.config, "params"):
                            min_required_length = model.config.params.get("sequence_len", 30)
                        elif hasattr(model, "config") and isinstance(model.config, dict):
                            min_required_length = model.config.get("params", {}).get("sequence_len", 30)
                    
                    # 对于深度学习模型，如果数据不足，跳过该窗口
                    # 这是正常的业务逻辑（数据不足时跳过），使用 debug 级别记录即可
                    test_window_size = test_end - test_start
                    if is_deep_learning_model and test_window_size < min_required_length:
                        logger.debug(
                            f"模型 {model_name} 在窗口 [{test_start}:{test_end}] 跳过回测: "
                            f"测试窗口只有 {test_window_size} 个交易日，需要至少 {min_required_length} 个交易日"
                        )
                        continue
                    
                    result = self.backtest(
                        model,
                        X,
                        None,
                        prices,
                        window_start,
                        window_end,
                        test_start,
                        test_end,
                    )
                    results[model_name].append(result)
                except Exception as e:
                    logger.error(f"模型 {model_name} 在窗口 [{test_start}:{test_end}] 回测失败: {e}")

            # 基准策略：买入并持有
            prices_window = prices.iloc[test_start:test_end]
            if not prices_window.empty:
                baseline_results.append(self._compute_baseline_result(prices_window))

            # 移动到下一个窗口
            current_train_end += self.config.step_size

        if baseline_results:
            results["buy_and_hold"] = baseline_results

        return results

    def _compute_baseline_result(self, prices_window: pd.DataFrame) -> BacktestResult:
        """计算单个测试窗口的买入并持有基准结果."""
        # 延迟导入，避免循环导入
        from .baseline import BuyAndHoldStrategy
        
        # 延迟初始化基准策略
        if self._baseline_strategy is None:
            self._baseline_strategy = BuyAndHoldStrategy(self.config)
        
        # 回测框架使用英文列名
        close_prices = prices_window["close"]

        if close_prices.empty:
            # 如果窗口内没有价格数据，返回零变化的结果
            empty_index = prices_window.index
            equity_curve = pd.Series(
                [self.config.initial_capital] * len(empty_index),
                index=empty_index,
                dtype=float,
            )
            zero_returns = pd.Series([0.0] * len(empty_index), index=empty_index, dtype=float)
            return BacktestResult(
                equity_curve=equity_curve,
                trades=[],
                positions=[],
                daily_returns=zero_returns,
                total_trades=0,
                win_rate=0.0,
                stop_loss_count=0,
                take_profit_count=0,
            )

        # 使用基准策略计算日收益率
        returns = self._baseline_strategy.calculate_returns(close_prices)
        # 与价格索引对齐，首日收益设为 0 表示持仓建立
        returns = returns.reindex(close_prices.index, fill_value=0.0).astype(float)
        if len(returns) > 0:
            returns.iloc[0] = 0.0

        # 基于日收益率构建净值曲线，初始资金为 initial_capital
        equity_curve = (1 + returns).cumprod() * self.config.initial_capital

        # 计算胜率（盈利交易日占比）
        positive_days = int((returns > 0).sum())
        win_rate = positive_days / len(returns) if len(returns) > 0 else 0.0

        return BacktestResult(
            equity_curve=equity_curve,
            trades=[],  # 买入并持有策略不记录逐笔交易
            positions=[],  # 简化：不跟踪逐日持仓
            daily_returns=returns,
            total_trades=0,
            win_rate=float(win_rate),
            stop_loss_count=0,
            take_profit_count=0,
        )

